解锁流畅、媲美原生应用的网页体验。本综合指南将深入探讨强大的 CSS 视图过渡伪元素,用于设计动态页面过渡样式,并提供实用示例与最佳实践。
精通 CSS View Transitions:深入解析伪元素样式
在不断演进的 Web 开发领域,追求无缝、流畅且引人入胜的用户体验是一个永恒的目标。多年来,开发者们一直致力于弥合 Web 与原生应用之间的差距,尤其是在页面过渡的平滑性方面。传统的网页导航通常会导致生硬的整页重载——一个空白的白屏会瞬间打破用户的沉浸感。单页应用(SPA)在一定程度上缓解了这个问题,但创建自定义、有意义的过渡仍然是一项复杂且通常很脆弱的任务,严重依赖 JavaScript 库和错综复杂的状态管理。
现在,CSS 视图过渡 API (CSS View Transitions API) 应运而生,这项颠覆性技术将彻底改变我们处理 Web 界面变化的方式。这个强大的 API 提供了一种简单而又极其灵活的机制,用于在不同的 DOM 状态之间创建动画,使得创建用户所期望的那种精致、媲美原生应用的体验变得前所未有的容易。该 API 的核心力量源于一组新的 CSS 伪元素。它们并非典型的选择器,而是由浏览器生成的动态、临时性元素,让您能够精细控制过渡的每一个阶段。本指南将带您深入探索这个伪元素树,探讨如何为每个组件设置样式,从而为全球受众构建出令人惊叹、高性能且易于访问的动画。
视图过渡的剖析
在为过渡设置样式之前,我们必须了解当过渡被触发时,底层发生了什么。当您启动一个视图过渡时(例如,通过调用 document.startViewTransition()),浏览器会执行一系列步骤:
- 捕获旧状态:浏览器为当前页面的状态拍摄一张“快照”。
- 更新 DOM:您的代码随后对 DOM 进行更改(例如,导航到新视图、添加或删除元素)。
- 捕获新状态:一旦 DOM 更新完成,浏览器会为新状态拍摄一张快照。
- 构建伪元素树:然后,浏览器在页面的顶层覆盖层中构建一个临时的伪元素树。该树包含了旧状态和新状态的快照图像。
- 执行动画:CSS 动画被应用于这些伪元素,从而在旧状态和新状态之间创建平滑的过渡。默认效果是简单的交叉淡入淡出。
- 清理:动画完成后,伪元素树被移除,用户可以与新的、实时的 DOM 进行交互。
自定义的关键就在于这个临时的伪元素树。您可以把它想象成设计工具中的一组图层,临时放置在您的页面之上。您对这些图层拥有完全的 CSS 控制权。以下是您将要使用的结构:
- ::view-transition
- ::view-transition-group(*)
- ::view-transition-image-pair(*)
- ::view-transition-old(*)
- ::view-transition-new(*)
- ::view-transition-image-pair(*)
- ::view-transition-group(*)
让我们来逐一解析这些伪元素代表什么。
伪元素角色介绍
::view-transition:这是整个结构的根。它是一个单一元素,填充整个视口并位于所有其他页面内容之上。它充当所有过渡组的容器,是设置全局过渡属性(如持续时间或缓动函数)的绝佳位置。
::view-transition-group(*):对于每个独特的过渡元素(通过 view-transition-name CSS 属性标识),都会创建一个组。这个伪元素负责为其内容的位置和尺寸制作动画。如果您有一张卡片从屏幕的一侧移动到另一侧,那么实际移动的就是这个 ::view-transition-group。
::view-transition-image-pair(*):嵌套在组内部,这个元素充当旧视图和新视图的容器及裁剪蒙版。其主要作用是在动画期间保持诸如 border-radius 或 transform 之类的效果,并处理默认的交叉淡入淡出动画。
::view-transition-old(*):这代表元素在旧状态(DOM 更改前)的“快照”或渲染视图。默认情况下,它的动画效果是从 opacity: 1 变为 opacity: 0。
::view-transition-new(*):这代表元素在新状态(DOM 更改后)的“快照”或渲染视图。默认情况下,它的动画效果是从 opacity: 0 变为 opacity: 1。
根元素:为 ::view-transition 伪元素设置样式
::view-transition 伪元素是绘制整个动画的画布。作为顶层容器,它是定义应全局应用于过渡的属性的理想场所。默认情况下,浏览器提供了一组动画,但您可以轻松地覆盖它们。
例如,默认过渡是一个持续 250 毫秒的交叉淡入淡出。如果您想为网站上的每个过渡更改此设置,可以针对根伪元素进行操作:
::view-transition {
animation-duration: 500ms;
animation-timing-function: ease-in-out;
}
这条简单的规则现在使所有默认的页面淡化效果耗时增加一倍,并使用 'ease-in-out' 曲线,赋予它们略有不同的感觉。虽然您可以在这里应用复杂的动画,但通常最好用它来定义通用的计时和缓动函数,让更具体的伪元素处理详细的编排。
分组与命名:`view-transition-name` 的威力
开箱即用,无需任何额外工作,视图过渡 API 就为整个页面提供了一个交叉淡入淡出的效果。这由一个用于根元素的伪元素组来处理。该 API 的真正威力在于当您想要在不同状态之间为特定的、独立的元素创建过渡时才能得以释放。例如,让列表页面上的产品缩略图无缝地放大并移动到详情页面上的主产品图片位置。
为了告诉浏览器,在不同 DOM 状态下的两个元素在概念上是同一个,您需要使用 view-transition-name CSS 属性。此属性必须同时应用于起始元素和结束元素。
/* On the list page CSS */
.product-thumbnail {
view-transition-name: product-image;
}
/* On the detail page CSS */
.main-product-image {
view-transition-name: product-image;
}
通过给两个元素赋予相同的唯一名称(本例中为 'product-image'),您就在指示浏览器:“不要只是将旧页面淡出、新页面淡入,而是为这个特定元素创建一个特殊的过渡。” 浏览器现在将生成一个专用的 ::view-transition-group(product-image) 来独立于根元素的淡出效果来处理其动画。这就是实现流行的“变形”或“共享元素”过渡效果的基本概念。
重要提示:在过渡期间的任何特定时刻,一个 view-transition-name 都必须是唯一的。您不能同时有两个可见元素具有相同的名称。
深入样式设计:核心伪元素
在为我们的元素命名之后,现在可以深入研究如何为浏览器为它们生成的特定伪元素设置样式。在这里,您可以创建真正定制化和富有表现力的动画。
`::view-transition-group(name)`:移动者
该组的唯一职责是从旧元素的大小和位置过渡到新元素的大小和位置。它不包含实际内容的外观,只包含其边界框。可以把它想象成一个移动的框架。
默认情况下,浏览器为其 transform 和 width/height 属性制作动画。您可以覆盖此行为以创建不同的效果。例如,您可以通过沿曲线路径制作动画为其移动添加弧度,或者让它在移动过程中放大和缩小。
::view-transition-group(product-image) {
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
在这个例子中,我们仅对产品图片的移动应用了一个特定的缓动函数,使其感觉更具动感和物理性,而不影响页面其余部分的默认淡化效果。
`::view-transition-image-pair(name)`:裁剪与淡化器
嵌套在移动组内的 image-pair 持有旧视图和新视图。它充当一个裁剪蒙版,因此如果您的元素有 border-radius,image-pair 会确保内容在整个尺寸和位置动画过程中保持该圆角裁剪。它的另一个主要工作是协调旧内容和新内容之间的默认交叉淡入淡出。
您可能希望对这个元素进行样式设置,以确保视觉一致性或创建更高级的效果。一个需要考虑的关键属性是 isolation: isolate。如果您计划在子元素(旧视图和新视图)上使用高级的 mix-blend-mode 效果,这一点至关重要,因为它会创建一个新的堆叠上下文,并防止混合效果影响到过渡组之外的元素。
::view-transition-image-pair(product-image) {
isolation: isolate;
}
`::view-transition-old(name)` 与 `::view-transition-new(name)`:动画的主角
这些是代表您的元素在 DOM 更改前后视觉外观的伪元素。您的大部分自定义动画工作将在这里进行。默认情况下,浏览器使用 opacity 和 mix-blend-mode 对它们运行一个简单的交叉淡入淡出动画。要创建自定义动画,您必须首先关闭默认动画。
::view-transition-old(name),
::view-transition-new(name) {
animation: none;
}
一旦默认动画被禁用,您就可以自由地应用自己的动画了。让我们探讨几种常见的模式。
自定义动画:滑动
我们不想用交叉淡入淡出,而是让容器的内容滑入。例如,在文章之间导航时,我们希望新文章的文本从右侧滑入,而旧文章的文本则向左滑出。
首先,定义关键帧:
@keyframes slide-from-right {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes slide-to-left {
from { transform: translateX(0); }
to { transform: translateX(-100%); }
}
现在,将这些动画应用到名为 'article-content' 的元素的旧伪元素和新伪元素上。
::view-transition-old(article-content) {
animation: 300ms ease-out forwards slide-to-left;
}
::view-transition-new(article-content) {
animation: 300ms ease-out forwards slide-from-right;
}
自定义动画:3D 翻转
为了获得更具戏剧性的效果,您可以创建一个 3D 卡片翻转效果。这需要使用 rotateY 为 transform 属性制作动画,并同时管理 backface-visibility。
/* The group needs a 3D context */
::view-transition-group(card-flipper) {
transform-style: preserve-3d;
}
/* The image-pair also needs to preserve the 3D context */
::view-transition-image-pair(card-flipper) {
transform-style: preserve-3d;
}
/* The old view flips from 0 to -180 degrees */
::view-transition-old(card-flipper) {
animation: 600ms ease-in forwards flip-out;
backface-visibility: hidden;
}
/* The new view flips from 180 to 0 degrees */
::view-transition-new(card-flipper) {
animation: 600ms ease-out forwards flip-in;
backface-visibility: hidden;
}
@keyframes flip-out {
from { transform: rotateY(0deg); }
to { transform: rotateY(-180deg); }
}
@keyframes flip-in {
from { transform: rotateY(180deg); }
to { transform: rotateY(0deg); }
}
实践示例与高级技巧
理论很有用,但实践应用才是我们真正学习的地方。让我们来看一些常见场景以及如何使用视图过渡伪元素来解决它们。
示例:“变形”的卡片缩略图
这是经典的共享元素过渡。想象一个用户个人资料的画廊。每个个人资料都是一张带有头像的卡片。当您点击一张卡片时,您会导航到一个详情页面,该页面顶部显着位置显示着同一个头像。
第一步:分配名称
在您的画廊页面中,头像图片获得一个名称。该名称对于每张卡片都应该是唯一的,例如,基于用户的 ID。
/* In gallery-item.css */
.card-avatar { view-transition-name: avatar-user-123; }
在个人资料详情页面上,大的头部头像获得完全相同的名称。
/* In profile-page.css */
.profile-header-avatar { view-transition-name: avatar-user-123; }
第二步:自定义动画
默认情况下,浏览器会移动和缩放头像,但它也会对内容进行交叉淡入淡出。如果图像是相同的,这个淡出效果就是不必要的,并且可能会导致轻微的闪烁。我们可以禁用它。
/* The star (*) here is a wildcard for any named group */
::view-transition-image-pair(*) {
/* Disable the default fade */
animation-duration: 0s;
}
等等,如果我们禁用了淡出效果,内容是如何切换的呢?对于旧视图和新视图完全相同的共享元素,浏览器足够智能,会在整个过渡过程中只使用一个视图。`image-pair` 实际上只持有一张图片,所以禁用淡出效果只是揭示了这一优化。对于内容实际发生变化的元素,您将需要一个自定义动画来替代默认的淡出效果。
处理宽高比变化
当一个过渡元素的宽高比发生变化时,会出现一个常见的挑战。例如,一个列表页面上的 16:9 横向缩略图可能会过渡到详情页面上的 1:1 正方形头像。浏览器的默认行为是独立地为宽度和高度制作动画,这会导致图像在过渡期间出现被压扁或拉伸的情况。
解决方案非常优雅。我们让 ::view-transition-group 处理尺寸和位置的变化,但我们覆盖其内部旧图像和新图像的样式。
目标是让旧的和新的“快照”在不失真的情况下填充其容器。我们可以通过将它们的宽度和高度设置为 100%,并允许浏览器默认的 object-fit 属性(继承自原始元素)来正确处理缩放来实现这一点。
::view-transition-old(hero-image),
::view-transition-new(hero-image) {
/* Prevent distortion by filling the container */
width: 100%;
height: 100%;
/* Override the default cross-fade to see the effect clearly */
animation: none;
}
有了这段 CSS,`image-pair` 将会平滑地为其宽高比制作动画,而内部的图像将根据其 `object-fit` 值被正确地裁剪或添加黑边,就像它们在普通容器中一样。然后,您可以在这个修正后的几何形状之上添加您自己的自定义动画,比如交叉淡入淡出。
调试与浏览器支持
为仅存在零点几秒的元素设置样式可能很棘手。幸运的是,现代浏览器为此提供了出色的开发者工具。在 Chrome 或 Edge DevTools 中,您可以转到“Animations”面板,当您触发视图过渡时,您可以暂停它。在动画暂停的情况下,您可以使用“Elements”面板像检查 DOM 的任何其他部分一样检查整个 `::view-transition` 伪元素树。您可以看到正在应用的样式,甚至可以实时修改它们以完善您的动画。
截至 2023 年末,视图过渡 API 已在基于 Chromium 的浏览器(Chrome、Edge、Opera)中得到支持。Firefox 和 Safari 的实现正在进行中。这使其成为渐进增强的完美候选者。拥有受支持浏览器的用户将获得愉悦的增强体验,而其他浏览器的用户则获得标准的、即时的导航。您可以在 CSS 中检查支持情况:
@supports (view-transition: none) {
/* All view-transition styles go here */
::view-transition-old(my-element) { ... }
}
面向全球受众的最佳实践
在实施动画时,考虑全球范围内多样化的用户和设备至关重要。
性能:动画应该快速而流畅。坚持为浏览器处理成本较低的 CSS 属性制作动画,主要是 transform 和 opacity。为 width、height 或 margin 等属性制作动画可能会在每一帧都触发布局重新计算,导致卡顿和糟糕的体验,尤其是在性能较差的设备上。
可访问性:一些用户会因动画而感到晕动症或不适。所有主流操作系统都提供了减少动态效果的用户偏好设置。我们必须尊重这一点。prefers-reduced-motion 媒体查询允许您为这些用户禁用或简化您的动画。
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
/* Skip all custom animations and use a quick, simple fade */
animation: none !important;
}
}
用户体验 (UX):好的过渡是有目的的。它们应该引导用户的注意力,并提供有关 UI 中正在发生的变化的上下文。过慢的动画会使应用程序感觉迟钝,而过于花哨的动画则可能分散注意力。目标过渡持续时间应在 200 毫秒到 500 毫秒之间。目标是让动画更多地被感觉到,而不是被看到。
结论:未来是流动的
CSS 视图过渡 API,特别是其强大的伪元素树,代表了 Web 用户界面的巨大飞跃。它为开发者提供了一个原生的、高性能的、高度可定制的工具集,以创建那种曾经是原生应用专属的流畅、有状态的过渡效果。通过理解 ::view-transition、::view-transition-group 以及 old/new 图像对的角色,您可以超越简单的淡入淡出,编排复杂、有意义的动画,从而增强可用性并取悦用户。
随着浏览器支持的扩展,这个 API 将成为现代前端开发者工具箱中不可或缺的一部分。通过拥抱其功能并遵循性能和可访问性的最佳实践,我们可以构建一个不仅功能更强大,而且对世界各地的每个人都更美观、更直观的 Web。